/**
 * Created with IntelliJ IDEA.
 * User: zzl
 * Date: 12-11-12
 * To change this template use File | Settings | File Templates.
 */
(function ($, undefined) {

    var activeEditor;
    var g_lastMousedownedEl;
    var g_timerId = 0;
    var g_onSuppressEditEvent;

    var BaseEditor = function (textBlock) {
        this.$textBlock = $(textBlock);
        this.selectAll = false;
    };
    BaseEditor.prototype = {
        constructor: BaseEditor,
        elStyle: "display: none; z-index: 999; " +
            "position: absolute; border: solid 1px black;",
        setupElEvents: function () {
            var me = this;
            me.$el.on('keydown', function (e) {
                me.editor_keydown(e);
            });
            me.$el.on('keyup input', function (e) {
                var val = me.$el.val();
                if (val != me.lastElVal) {
                    me.lastElVal = val;
                    me.editor_change(e);
                }
            });
            me.$el.on('propertychange', function (e) {
                if('value' !== e.originalEvent.propertyName) {
                    return;
                }
                var val = me.$el.val();
                if (val != me.lastElVal) {
                    me.lastElVal = val;
                    me.editor_change(e);
                }
            });
        },
        //

        isEditorMouseDown: function (e) {
            var me = this;
            if (e.target == me.$el[0]) {
                return true;
            }
            if (me.$el.data('mousedown_host')) {
                return true;
            }
            return false;
        },

        //
        editor_change: function (e) {
            var me = this;
            me.updateEditorSize();
        },
        editor_keydown: function (e) {
            var me = this;
            if (me.previewKeydown && me.previewKeydown(e)) {
                return;
            }
            var keyCode = e.which;
            var eat = false;
            if (keyCode == 13) {
                me.endEdit();
                eat = true;
            }
            else if (keyCode == 27) {
                me.endEdit(true);
                eat = true;
            }
            else if (keyCode == 8 && jsex.msie) {
                setTimeout(function () {
                    me.$el.triggerHandler('input');
                }, 1);
            }
            else if(keyCode == 9) { // tab
                eat = true;
            }
            if (eat) {
                e.preventDefault();
            }
        },
        beginEdit: function (caretAhead) {
            var me = this;
            me.$el = me.createEl();
            me.$el.appendTo(document.body);

            var editText = me.getEditText();
            me.oriText = editText;
            me.$el.val(editText);

            me.setupElEvents();
            me.setEditorStyles();
            me.updateEditorSize();
            me.alignEditor();
            me.$textBlock.css({visibility: 'hidden', opacity: 0});

            me.$el.show();

            me.onEditorShow(caretAhead);
        },
        focus: function() {
            var me = this;
            me.$el.focus();
        },
        endEdit: function (cancel) {
            var me = this;
            me.$textBlock.css({visibility: '', opacity: 1});
            setTimeout(function() {
                me.removeEl();
                if(!$(document.activeElement).is('.editable')) {
                    me.$textBlock.focus();
                }
            }, 0);
            var text = me.$el.val();
            activeEditor = null;
            if (cancel) {
                return;
            }
            if (jsex.indexOf(text, '\n') != -1) {
                me.$textBlock.addClass('multiline');
            }
            else {
                me.$textBlock.removeClass('multiline');
            }
            me.updateTextBlock(text);
            if (me.oriText != text) {
                me.$textBlock.trigger('edited', [me.oriText, text]);
            }
        },
        hasSelection: function() {
            var me = this;
            var selection = domex.getSelection(me.$el[0]);
            return selection.end != selection.start;
        },
        paste: function(pasteText) {
            var me = this;
            var selection = domex.getSelection(me.$el[0]);
            var text = me.$el.val();
            domex.clearSelection();
            text = text.substring(0, selection.start) + pasteText + text.substring(selection.end);
            me.$el.val(text);
            me.editor_change();
            domex.setSelection(me.$el[0], selection.start + pasteText.length);
        },
        getSelectedText: function() {
            var me = this;
            var selection = domex.getSelection(me.$el[0]);
            var text = me.$el.val();
            return text.substring(selection.start, selection.end);
        },
        updateTextBlock: function (text) {
            var me = this;
            var html = undecorated.plainTextToHtml(text, true);
            me.$textBlock.html(html);
        },
        setEditorStyles: function() {
            var me = this;
            var height = me.$textBlock.height();
            var lineHeightEx = parseInt(me.$textBlock.css('border-bottom-width'))||-1;
            var css = {
                fontFamily: me.$textBlock.css('fontFamily'),
                fontSize: me.$textBlock.css('fontSize'),
                paddingTop: me.$textBlock.css('padding-top'),
                paddingLeft: me.$textBlock.css('padding-left'),
                paddingBottom: me.$textBlock.css('padding-bottom'),
                lineHeight: (height + lineHeightEx)+ 'px',
                height: height + 'px'
            };
            me.$el.css(css);
        },
        updateEditorSize: function () {
            var me = this;

            var dim = undecorated.measure(me.$textBlock, me.$el.val() + ' ');
            var width = dim.width + 5;

            var $containerTd = me.$textBlock[0].nodeName == 'TD'?
                me.$textBlock: me.$textBlock.parents('td:first');
            if($containerTd.length) {
                var offset = $containerTd.offset();
                var tbOffset = me.$textBlock.offset();
                var fitWidth = $containerTd.width() - (tbOffset.left - offset.left) + 2;
                if(width > fitWidth) {
                    width = fitWidth;
                }
            }

            me.$el.width(width);
        },
        getEditText: function () {
            var me = this;
            var text = undecorated.htmlToPlainText(me.$textBlock.html());
            return text;
        },
        alignEditor: function () {
            var me = this;
            var pos = me.$textBlock.offset();

            var borderLeftWidth = parseInt(me.$textBlock.css('border-left-width')) || 0;
            var borderTopWidth = parseInt(me.$textBlock.css('border-top-width')) || 0;
            var top = pos.top - 1;// - (jsex.msie ? 2 : 1) + borderTopWidth;
            var left = pos.left - 1;
            var css = {
                left: left + 'px',
                top: top + 'px'
            };
            me.$el.css(css);
            me.$el.trigger('input');
        },
        onEditorShow: function (caretAhead) {
            var me = this;
            var caretIndex = caretAhead? 0: me.$el.val().length;
            domex.setSelection(me.$el[0], me.selectAll ? 0 : caretIndex, caretIndex);

            // make editor in view
            var bottom = me.$el.offset().top + me.$el.outerHeight();
            var windowScrollTop = $(window).scrollTop();
            var windowBottom = windowScrollTop + document.documentElement.clientHeight;
            if(bottom > windowBottom) {
//                alert('..');
                $(window).scrollTop(bottom - document.documentElement.clientHeight + 4);
            }
        },
        removeEl: function () {
            var me = this;
            me.$el.remove();
        }
    };

    var SingleLineEditor = function () {
        BaseEditor.apply(this, arguments);
    };
    SingleLineEditor.prototype = $.extend(new BaseEditor(), {
        constructor: SingleLineEditor,

        createEl: function () {
            return $('<input class="editor" type="text" style="' + this.elStyle + '"/>');
        }
    });

    //
    var MultiLineEditor = function () {
        BaseEditor.apply(this, arguments);
    };
    MultiLineEditor.prototype = $.extend(new BaseEditor(), {
        constructor: MultiLineEditor,

        createEl: function () {
            return $('<textarea style="' + this.elStyle + ';overflow:hidden;resize:none;"></textarea>');
        },

        editor_keydown: function (e) {
            var me = this;
            if (e.which == 13) {
                if (e.ctrlKey) {
                    me.endEdit();
                    e.preventDefault();
                }
            }
            else {
                BaseEditor.prototype.editor_keydown.apply(this, arguments);
            }
        },

        updateEditorSize: function () {
            var me = this;

            var dim = undecorated.measure(me.$textBlock, me.$el.val() + ' ');
            me.$el.css('line-height', '');
            me.$el.width(dim.width + 5);
            me.$el.height(dim.height + 2);
        }
    });

    var TransformingMultiLineEditor = function () {
        MultiLineEditor.apply(this, arguments);
    };

    TransformingMultiLineEditor.prototype = $.extend(new MultiLineEditor(), {
        constructor: TransformingMultiLineEditor,

        getEditText: function () {
            var me = this;
            return me.text;
        },

        onEditorShow: function () {
            var me = this;
            domex.setSelection(me.$el[0], me.caretIndex);
        }
    });

    //
    var CustomInputEditor = function (textBlock, input, config) {
        SingleLineEditor.apply(this, [textBlock]);
        this.$input = $(input);
        $.extend(this, config);
    };
    CustomInputEditor.prototype = $.extend(new SingleLineEditor(), {
        constructor: CustomInputEditor,

        createEl: function () {
            return this.$input;
        },
        removeEl: function () {
            this.$input.hide();
        },
        updateEditorSize: function () {
            var me = this;
            me.$el.width(me.$textBlock.width());
            me.$el.height(me.$textBlock.height());
        }
    });


    //
    $(function () {

        $(document).on('mousedown.editable', document_mousedown);
//        $(document).on('selectionchange.editable', function() {
//            g_onSuppressEditEvent = true;
//        });
        $(document).on('mouseup.editable', function(e) {
            g_onSuppressEditEvent = false;
        });

    });

    function document_mousedown(e) {
        g_lastMousedownedEl = e.target;
        if (!activeEditor) {
            return;
        }
        if (activeEditor.isEditorMouseDown(e)) {
            return;
        }
        activeEditor.endEdit();
        g_onSuppressEditEvent = true;
    }

    //
    var Editable = function (element, options) {
        this.$element = $(element);
        this.options = options;
        this.setup();
    };

    var proto = {constructor: Editable};
    Editable.prototype = proto;

    proto.setup = function () {
        var me = this;

        var handlers = {
            mousedown: function (e) {
                me.textBlock_mousedown(this, e);
            },
            dblclick: function () {
                clearTimeout(g_timerId);
            },
            mouseup: function (e) {
                me.textBlock_mouseup(this, e);
            },
            editRequest: function () {
                me.edit($(this));
            },
            keydown: function (e) {
                me.textBlock_keydown(this, e);
            }
        };
        me.$element.on(handlers, me.options.selector);

        me.$element.find(me.options.selector).attr('tabindex', '0');
    };

    proto.textBlock_mousedown = function (textBlock, e) {
        var me = this;
//        console.log('@@@@@@@ textBlock_mousedown @' + new Date());

        me.focusedBeforeMousedown = $(textBlock).is(':focus') ||
            textBlock == g_lastMousedownedEl ||
            $.contains(textBlock, g_lastMousedownedEl);

        // for ie 10
        textBlock.focus();

        clearTimeout(g_timerId);
        g_timerId = null;
    };

    proto.textBlock_mouseup = function (textBlock, e) {
        var me = this;
        var lastUpButton = me.lastUpButton;
        me.lastUpButton = e.which;
        if (!me.focusedBeforeMousedown || e.which != 1 || lastUpButton != 1 ||
            e.ctrlKey || e.shiftKey) {
            return;
        }
        if(g_onSuppressEditEvent) {
//            console.log('@@@@@@@ meet g_onSuppressEditEvent @' + new Date());
            g_onSuppressEditEvent = false;
            return;
        }
        //console.log('edit!');
        clearTimeout(g_timerId);
        var $tb = $(textBlock);
        if($tb.data('disableEdit')) { //hack
            return;
        }

//        console.log('@@@@@@@ textBlock_mouseup & setTimeout @' + new Date());
        g_timerId = setTimeout(function () {
            if ($tb.is(':focus') ||
                $tb[0] == g_lastMousedownedEl ||
                $.contains($tb[0], g_lastMousedownedEl)) {

                me.edit($tb);
            }
            g_timerId = null;
        }, 350);
    };

    proto.textBlock_keydown = function (textBlock, e) {
        var me = this;
        var keyCode = e.keyCode;
        if (keyCode == 113) { // F2
            e.preventDefault();
            me.edit(textBlock);
        }
    };

    proto.edit = function (textBlock, caretAhead) {
        var me = this;
        var $textBlock = $(textBlock);
        me.$editingTextBlock = $textBlock;

        var text = undecorated.htmlToPlainText($textBlock.html());
        var multiline = jsex.indexOf(text, '\n') != -1;

        var editor;

        if (me.options.createEditor) {
            editor = me.options.createEditor($textBlock);
        }
        if (!editor) {
            editor = multiline ?
                new MultiLineEditor($textBlock) :
                new SingleLineEditor($textBlock);
        }

        activeEditor = editor;
        editor.beginEdit(caretAhead);

        if (!multiline) {
            editor.previewKeydown = function (e) {
                return me.preview_singleLineEditor_keydown(e);
            }
        }
    };

    proto.preview_singleLineEditor_keydown = function (e) {
        var me = this;
        if (e.which != 13 || !e.shiftKey) {
            return false;
        }
        e.preventDefault();

        var input = activeEditor.$el[0];
        var selection = domex.getSelection(input);
        var text = input.value.substring(0, selection.start) + '\n' +
            input.value.substr(selection.end);

        activeEditor.endEdit(true);

        var tmle = new TransformingMultiLineEditor(me.$editingTextBlock);
        tmle.text = text;
        tmle.caretIndex = selection.start + 1;

        activeEditor = tmle;
        activeEditor.beginEdit();

        return true;
    };

    proto.commit = function () {
        var me = this;
        if (activeEditor != null) {
            activeEditor.endEdit();
        }
    };

    proto.isEditing = function() {
        var me = this;
        return activeEditor != null;
    };

    proto.hasSelection = function() {
        var me = this;
        return activeEditor.hasSelection();
    };

    proto.paste = function(text) {
        var me = this;
        activeEditor && activeEditor.paste(text);
    };

    proto.getSelectedText = function() {
        return activeEditor? activeEditor.getSelectedText(): '';
    };

    proto.focus = function() {
        var me = this;
        activeEditor.focus();
    };

    proto.cancelIfEditing = function() {
        var me = this;
        if(activeEditor) {
            activeEditor.endEdit(true);
        }
    };

    $.fn.editable = $.addPluginFn('editable', Editable, {
        selector: '.editable',
        editor: undefined
    });

    $.fn.getEditText = function () {
        return undecorated.htmlToPlainText(this.html());
    };

    //
    undecorated.Editable = {
        BaseEditor: BaseEditor,
        CustomInputEditor: CustomInputEditor
    };

})(jQuery);